/*
 * Copyright 2011 Nicolas Lochet Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License. You may obtain a copy of the License at
 *	
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and limitations under the License.
 */
package org.nicocube.gwt.parser.stepper.matcher.client;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.nicocube.gwt.parser.common.client.Content;
import org.nicocube.gwt.parser.common.client.Stepper;
import org.nicocube.gwt.parser.token.client.Token;
import org.nicocube.gwt.parser.token.client.TokenImpl;
import org.nicocube.gwt.parser.token.client.TokenType;

/**
 * An implementation of stepper based on Matcher
 * 
 * @author nicolas
 *
 * @param <T>
 */
public class MatcherStepper<T extends TokenType> implements Stepper<T> {

	/**
	 * The List of the exact kind of tokenType
	 */
	private final List<T> exact;

	/**
	 * The Set of unique first character from all exact tokenType
	 */
	private final Set<Character> exactFirst;

	/**
	 * The unique and compulsory catchall tokenType
	 */
	private T catchall;

	/**
	 * Give an array of TokenType initialize the Lexer
	 * 
	 * @param tokenTypes
	 * @throws ClassCastException if one of the 
	 * @throws IllegalStateException if no CatchAllTokenMatcher is 
	 */
	public MatcherStepper(T[] tokenTypes) {
		exact = new LinkedList<T>();
		exactFirst = new HashSet<Character>();
		for (T t : tokenTypes) {
			TokenMatcher tm = ((MatchableToken)t).matcher();

			Set<Character> firstChars = tm.firstChars();
			if (firstChars != null) {
				exactFirst.addAll(firstChars);
				exact.add(t);
			} else {
				if (catchall == null)
					catchall = t;
				else 
					throw new IllegalStateException("Only one CatchallTokenMatcher should exist for an enum of TokenType.");
			}
		}
		if (catchall == null)
			throw new IllegalStateException("At least one CatchallTokenMatcher should exist for an enum of TokenType.");
	}

	@Override
	public Token<T> step(Content content, int beginIndex, int endIndex) {

		int cur = beginIndex;
		int prev = cur;

		while (cur < endIndex) {

			// first we try to feed CATCHALL
			while (cur < endIndex && !exactFirst.contains(content.charAt(cur))) {
				cur++;
			}
			// a sequence before a first char from exactFirst exists, we must create a Token of catchall type
			if (cur>prev) {
				return new TokenImpl<T>(catchall,content,prev,cur);
			}

			if (cur < endIndex) {
				int max = 0;
				T type=null;
				for(T t : exact) {
					TokenMatcher tm = ((MatchableToken)t).matcher();

					Match match = tm.match(content.substring(cur));

					if (match.ok() && match.length() > max) {
						max = match.length();
						type = t;
					}
				}
				if (type != null) {
					return new TokenImpl<T>(type, content, cur, cur+max);
				} else {
					// if no token found increment cur to have it as the first character in the next CATCHALL search sequence
					cur++;
				}
			}
		}
		// this should never occur, but any way if we arrive here, that's what we should expect
		return new TokenImpl<T>(catchall,content,beginIndex,endIndex);
	}
}
